package jsonx

import (
	"strconv"
)

type Marshaler interface {
	Len() int
	MarshalJsonx([]byte) (int, error)
}

func Marshal(m Marshaler) ([]byte, error) {
	var buf = make([]byte, m.Len())
	n, err := m.MarshalJsonx(buf)
	if err != nil {
		return nil, err
	}
	return buf[:n], nil
}

const (
	digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
	digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
)

func FormatBits(buf []byte, u uint64, neg bool) int {
	if neg {
		u = -u
	}

	if u < 10 {
		if neg {
			buf[0] = '-'
			buf[1] = byte(u) + 48
			return 2
		}
		buf[0] = byte(u) + 48
		return 1
	}

	var q uint64
	var j uintptr
	var a [20]byte
	i := 20

	for u >= 100 {
		i -= 2
		q = u / 100
		j = uintptr(u - q*100)
		a[i+1] = digits01[j]
		a[i] = digits10[j]
		u = q
	}
	if u >= 10 {
		i--
		q = u / 10
		a[i] = digits01[uintptr(u-q*10)]
		u = q
	}
	i--
	a[i] = digits01[uintptr(u)]

	if neg {
		i--
		a[i] = '-'
	}
	n := copy(buf, a[i:])
	return n
}

var maxFloatConvert = float64(10000000000000000000)

func FormatFloat(buf []byte, u float64) int {
	var (
		i   int
		p   uint64
		neg bool
	)
	if u < 0 {
		neg = true
		u = -u
	}
	if u > maxFloatConvert {
		fb := strconv.FormatFloat(u, 'f', 2, 64)
		i += copy(buf[i:], fb)
		return i
	}
	m := uint64(u * 100)
	t := m / 100
	l := m - t*100
	m = uint64(u * 1000)
	t = m / 10
	n := m - t*10
	if n > 4 {
		l += 1
	}
	if l == 100 {
		l = 0
		p = 1
	}
	if neg {
		buf[i] = '-'
		i += 1
	}
	i += FormatBits(buf[i:], uint64(u)+p, false)
	if l > 0 {
		buf[i] = '.'
		i += 1
		i += FormatBits(buf[i:], uint64(l), false)
	}
	return i
}

func hex(c byte) byte {
	const chars = "0123456789abcdef"
	return chars[c&0xf]
}

func AppendString(buf []byte, v string) int {

	var (
		p, j int
	)

	for i := 0; i < len(v); i++ {
		c := v[i]
		var escape byte
		switch c {
		case '\t':
			escape = 't'
		case '\r':
			escape = 'r'
		case '\n':
			escape = 'n'
		case '\\':
			escape = '\\'
		case '"':
			escape = '"'
		default:
			if c >= 0x20 {
				// no escaping is required
				continue
			}
		}
		if escape != 0 {
			j += copy(buf[j:], v[p:i])
			buf[j] = '\\'
			j += 1
			buf[j] = escape
			j += 1
		} else {
			j += copy(buf[j:], v[p:i])
			j += copy(buf[j:], `\u00`)
			buf[j] = hex(c >> 4)
			j += 1
			buf[j] = hex(c)
			j += 1
		}
		p = i + 1
	}
	j += copy(buf[j:], v[p:])
	return j
}
